#include <linux/interrupt.h>
#include <linux/percpu.h>
#include <linux/irq.h>
#include <linux/threads.h>
#include <linux/cpu.h>

#include <riscv/csr-ops.h>

#include "plic.h"

static DEFINE_PER_CPU(struct plic_handler, plic_handlers);
static unsigned int plic_parent_irq = RV_IRQ_EXT;

static struct __irqaction _act[32] = {};

int request_percpu_irq(unsigned int irq, irq_handler_t handler,
                       const char *devname, void *percpu_dev_id)
{
    struct __irqaction *ia = _act;

    ia[irq].handler = handler;
    ia[irq].flags = 0;
    ia[irq].dev_id = percpu_dev_id;
    ia[irq].irq = irq;

    return 0;
}

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
                const char *name, void *dev)
{
    struct plic_handler *ph = this_cpu_ptr(&plic_handlers);
    struct __irqaction *ia = _act;

    ia[irq].handler = handler;
    ia[irq].flags = flags;
    ia[irq].dev_id = dev;
    ia[irq].irq = irq;

    plic_set_priority(ph, irq, 1);
    plic_irq_enable(ph, irq);

    return 0;
}

static int plic_dying_cpu(unsigned int cpu)
{
    if (plic_parent_irq)
    {
    }

    return 0;
}

static int plic_starting_cpu(unsigned int cpu)
{
    struct plic_handler *handler = this_cpu_ptr(&plic_handlers);

    if (plic_parent_irq)
    {
        csr_set(CSR_IE, 1 << RV_IRQ_EXT);
    }

    plic_set_threshold(handler, PLIC_ENABLE_THRESHOLD);

    return 0;
}

static void plic_handle_irq(struct pt_regs *regs)
{
    int irq;

    if ((regs->cause & 0xff) == 5)
    {
        irq = 5;
        if (_act[irq].handler)
        {
            _act[irq].handler(irq, _act[irq].dev_id);
        }
    }
    else
    {
        struct plic_handler *handler = this_cpu_ptr(&plic_handlers);

        while ((irq = plic_claim(handler)))
        {
            if (_act[irq].handler)
            {
                _act[irq].handler(irq, _act[irq].dev_id);
            }

            plic_complete(handler, irq);
        }
    }
}

void irqchip_init(void)
{
    struct plic_handler *handler;
    int i, nr_contexts = NR_CPUS;
    void *plic_base = (void *)0x0c000000;

    set_handle_irq(plic_handle_irq);

    handler = per_cpu_ptr(&plic_handlers, 0);
    plic_handler_init(handler, plic_base, 1); // cpu0
#if NR_CPUS > 1
    handler = per_cpu_ptr(&plic_handlers, 1);
    plic_handler_init(handler, plic_base, 3); // cpu1
#endif

    cpuhp_setup_state(CPUHP_AP_IRQ_CHIP_STARTING,
                      "irqchip/sifive/plic:starting",
                      plic_starting_cpu, plic_dying_cpu);
}
